//***************************************************************************************

//                                           主函数
//          总共实现了亚像素的边缘、直线、角点、斑点、圆弧检测以及模板、特征、形状匹配八个功能
//                              按任意键退出，按键1～8分别为实现上述8种功能

//***************************************************************************************

#include "ImageDecMat.hpp"


void ShowHelpText()
{
    
    //说明文字,帮助文档
    printf("\n\n------------------------------------------------------------------\n");
    printf("\n\n\t\t\t总共实现了亚像素的边缘、直线、角点、斑点、圆弧检测以及模板、特征、形状匹配八个功能。\n");
    printf("\n\n\t\t\t按任意键退出，按键1～8分别为实现上述8种功能\n");
    printf("\n\n\t\t\t按键1，利用canny算子实现边缘检测-其中canny边缘低阈值为100，高阈值为200");
    printf("\n\n\t\t\t按键2，利用累积概率霍夫变换实现亚像素直线-其中canny边缘低阈值为50，高阈值为200，累积平面阈值为80");
    printf("\n\n\t\t\t按键3，利用Shi-Tomasi算法实现亚像素角点检测-其中角点最大数量为150");
    printf("\n\n\t\t\t按键4，利用Blob分析实现亚像素斑点检测-其中检测最小区域为100，最大区域为8000");
    printf("\n\n\t\t\t按键5，利用霍夫圆变换实现亚像素圆弧检测-canny高阈值为200，圆心累加阈值为110");
    printf("\n\n\t\t\t按键6，实现模板匹配-0代表平方差匹配法，1代表归一化平方差匹配法，2代表相关匹配法，3代表归一化相关匹配法，4代表系数匹配法，5代表归一化系数匹配法，本例子使用的方法5");
    printf("\n\n\t\t\t按键7，利用ORB算法提取特征并利用FLANN进行匹配 实现特征匹配");
    printf("\n\n\t\t\t按键8，利用轮廓信息实现形状匹配-其中canny边缘低阈值为100，高阈值为200，且对轮廓图使用模板匹配方法也是5:归一化系数匹配法");
    printf("\n\n------------------------------------------------------------------\n");
    
}

int main()
{
    //显示帮助说明文字
    ShowHelpText();
    ImageDecMat process;
    cv::Mat srcImage,templateImage,dstImage;
    //载入源图像srcImage
    srcImage=cv::imread("/Users/new/Desktop/3.jpg");
    if(!srcImage.data){printf("读取源图像srcImage错误～！\n");return false;}
    //载入模板图片templaImage
    templateImage=cv::imread("/Users/new/Desktop/4.jpg");
    if(!templateImage.data){printf("读取模板图像templateImage错误～！\n");return false;}
    //创建窗口以及滚动条
    imshow("image[origin]",srcImage);//显示源图像
    
    
    //***********************参数定义************************************
    int key=cvWaitKey(0);//获取键盘按键
    //根据按键的不同，进行各种操作，按键为1～7
    switch((char)key)
    {
        case '1'://按'1'，进行边缘检测
            std::cout<<"按键'1'被按下，进行亚像素边缘检测操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 1, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        case '2'://按'2'，进行直线检测
            std::cout<<"按键'2'被按下，进行亚像素直线检测操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 2, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        case '3'://按'3'，进行角点检测
            std::cout<<"按键'3'被按下，进行亚像素角点检测操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 3, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        case '4'://按'4'，进行斑点检测
            std::cout<<"按键'4'被按下，进行亚像素斑点检测操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 4, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        case '5'://按'5'，进行圆弧检测
            std::cout<<"按键'5'被按下，进行亚像素圆弧检测操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 5, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        case '6'://按'6'，进行模板匹配操作
            std::cout<<"按键'6'被按下，进行模板匹配操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 6, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        case '7'://按'7'，进行特征匹配操作
            std::cout<<"按键'7'被按下，进行特征匹配操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 7, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        case '8'://按'8'，进行形状匹配操作
            std::cout<<"按键'8'被按下，进行形状匹配操作\n";
            process.Image_DetectMatch(srcImage, templateImage, 8, dstImage);
            std::cout<<"请按任意键退出\n";
            break;
        default:
            std::cout<<"请按ESC退出，或者按下 数字1～8进行相应操作\n";
    }
    cvWaitKey(0);
    return 0;
    
    
}

//***************************************************************************************

//                                  接口函数
//      总共实现了边缘、直线、角点、斑点、圆弧检测以及模板、特征、形状匹配八个功能。

//***************************************************************************************


//判断输入是否为灰度图，若是，则不进行灰度化处理，否则进行灰度化处理
bool bIsColor=true;

void ImageDecMat::Image_DetectMatch(cv::Mat src, cv::Mat tmp, int n, cv::Mat& dst)
{
    ImageDecMat a;
    if(bIsColor)
    {
        a.on_garyTrans(src, src);
        a.on_garyTrans(tmp, tmp);
    }
    //n为方法选择
    if(n==1)
    {
        a.on_edgeDetector(src, dst);
    }
    else if(n==2)
    {
        a.on_lineDetector(src, dst);
    }
    else if(n==3)
    {
        a.on_cornerDetector(src, dst);
    }
    else if(n==4)
    {
        a.on_spotDetector(src, dst);
    }
    else if(n==5)
    {
        a.on_arcDetector(src, dst);
    }
    else if(n==6)
    {
        a.on_matchTemplate(src, tmp, dst);
    }
    else if(n==7)
    {
        a.on_matchFeatures(src, tmp, dst);
    }
    else if(n==8)
    {
        a.on_shapeFeatures(src, tmp, dst);
    }
    else
    {
        std::cout<<"请输入正确的数字～！"<<std::endl;
    }
    imshow("image[process]",dst);
    
}
//***************************************************************************************

//                                  图像灰度化处理

//***************************************************************************************



void ImageDecMat::on_garyTrans(cv::Mat src,cv::Mat& dst)
{
    cv::cvtColor(src, dst, CV_BGR2GRAY);
}


//***************************************************************************************

//                利用canny算子实现边缘检测-其中canny边缘低阈值为100，高阈值为200

//***************************************************************************************

void ImageDecMat::on_edgeDetector(cv::Mat src,cv::Mat& dst)
{
    cv::Mat srcImage,edgeImage,dstImage;
    src.copyTo(srcImage);
    //平滑降噪
    blur(srcImage,edgeImage,cv::Size(3,3));
    //Canny算法边缘检测
    Canny(edgeImage,edgeImage,150,200,3);
    //将输出图像内所有元素置为0
    dstImage=cv::Scalar::all(0);
    //使用Canny算子输出的边缘图edgeImage作为掩码，将原图像srcImage拷到目标图dstImage中
    srcImage.copyTo(dstImage,edgeImage);
    //显示目标图
    dstImage.copyTo(dst);
}

//***************************************************************************************

//      利用累积概率霍夫变换实现直线检测-其中canny边缘低阈值为50，高阈值为200，累积平面阈值为80

//***************************************************************************************



void ImageDecMat::on_lineDetector(cv::Mat src,cv::Mat& dst)
{
    cv::Mat edgeImage;
    int lowThresh=50;
    int highThresh=200;
    int threshold=80;
    //Canny边缘检测以及灰度颜色转换
    Canny(src, edgeImage, lowThresh, highThresh,3);
    cv::cvtColor(edgeImage, dst, CV_GRAY2BGR);
    
    //进行SHT
    std::vector<cv::Vec4i> lines;
    HoughLinesP(edgeImage, lines, 1, CV_PI/180, threshold,50.0,10.0);
    //循环遍历绘制每条直线
    for(size_t i;i<lines.size();++i)
    {
        cv::Vec4i l=lines[i];
        line(dst, cv::Point(l[0],l[1]), cv::Point(l[2],l[3]), cv::Scalar(0,0,255),1,CV_AA);
    }
    
}

//***************************************************************************************

//              利用Shi-Tomasi算法实现角点检测-其中角点最大数量为120

//***************************************************************************************


void ImageDecMat::on_cornerDetector(cv::Mat src,cv::Mat& dst)
{
    int ncorner_max=120;//角点最大数量
    //Shi-Tomasi算法的参数准备
    std::vector<cv::Point2f>corners;
    double qualityLevel=0.01;//角点检测可接受的最小特征
    double minDistance=10;//角点之间的最小距离
    int blockSize=3;//计算导数自相关矩阵时指定的邻域范围
    double k=0.04;//权重系数
    dst=src.clone();//复制源图像到一个临时变量中，作为感兴趣区域
    //进行Shi-Tomasi角点检测
    goodFeaturesToTrack(src, corners, ncorner_max, qualityLevel, minDistance,cv::Mat(),blockSize,false,k);
    
    //输出文字信息
    std::cout<<">此次检测到的角点数量为："<<corners.size()<<std::endl;
    
    //亚像素角点检测的参数设置
    cv::Size winSize=cv::Size(5,5);
    cv::Size zeroZone=cv::Size(-1,-1);
    cv::TermCriteria criteria=cv::TermCriteria(cv::TermCriteria::EPS+cv::TermCriteria::MAX_ITER,400, 0.001);
    
    //计算出亚像素点角点位置
    cornerSubPix(src, corners, winSize, zeroZone, criteria);
    
    //输出角点信息
    for(int i=0;i<corners.size();++i)
    {
        std::cout<<"\t精确角点坐标["<<i<<"]("<<corners[i].x<<","<<corners[i].y<<")"<<std::endl;
    }
    //绘制检测到的角点
    int r=4;
    for(unsigned int i=0;i<corners.size();++i)
    {
        //以随机颜色绘制角点
        circle(dst, corners[i], r, cv::Scalar(0,0,255),-1,8,0);
        
    }
    
}

//***************************************************************************************

//                       利用Blobf分析实现斑点检测-其中检测最小区域为100，最大区域为8000

//***************************************************************************************


void ImageDecMat::on_spotDetector(cv::Mat src,cv::Mat& dst)
{
    cv::SimpleBlobDetector::Params params;
    params.minThreshold = 40;
    params.maxThreshold = 160;
    params.thresholdStep = 5;
    params.minArea = 100;//检测最小区域
    params.minConvexity = .05f;
    params.minInertiaRatio = .05f;
    params.maxArea = 8000;//检测最大区域
    
    cv::SimpleBlobDetector detector(params);
    
    std::vector<cv::KeyPoint> key_points;
    
    detector.detect(src, key_points);
    
    
    /*Mat output_img;
     output_img = img;*/
    drawKeypoints(src, key_points, dst, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
}

//***************************************************************************************

//              利用霍夫圆变换实现圆弧检测-canny高阈值为200，圆心累加阈值为110

//***************************************************************************************


void ImageDecMat::on_arcDetector(cv::Mat src,cv::Mat& dst)
{
    int highThresh=200;
    int spotThresh=110;
    //进行高斯平滑
    GaussianBlur(src, src, cv::Size(5,5), 2,2);
    //进行霍夫圆变换
    std::vector<cv::Vec3f> Circles;
    HoughCircles(src, Circles, CV_HOUGH_GRADIENT, 1.5, 15,highThresh,spotThresh,0,0);//canny高阈值为200，圆心累加阈值为110
    //循环遍历绘制圆
    for(size_t i=0;i<Circles.size();++i)
    {
        cv::Point center(cvRound(Circles[i][0]),cvRound(Circles[i][1]));
        int radius=cvRound(Circles[i][2]);
        //绘制圆心
        circle(src, center, 3, cv::Scalar(0,255,0),-1,8,0);
        //绘制圆轮廓
        circle(src, center, radius, cv::Scalar(155,50,255),3,8,0);
    }
    src.copyTo(dst);
    
}

//***************************************************************************************

//                              实现模板匹配-0代表平方差匹配法
//  1代表归一化平方差匹配法，2代表相关匹配法，3代表归一化相关匹配法，4代表系数匹配法，5代表归一化系数匹配法
//                              本例子使用的方法5

//***************************************************************************************


void ImageDecMat::on_matchTemplate(cv::Mat src,cv::Mat tmp,cv::Mat& dst)
{
    int matchMethod=5;//0代表平方差匹配法，1代表归一化平方差匹配法，2代表相关匹配法，3代表归一化相关匹配法，4代表系数匹配法，5代表归一化系数匹配法
    //用于结果输出的矩阵
    cv::Mat dstImage;
    int dstImage_rows=src.rows-src.rows+1;
    int dstImage_cols=src.cols-src.cols+1;
    dstImage.create(dstImage_rows,dstImage_cols, CV_32FC1);
    //进行匹配和归一化
    cv::matchTemplate(src, tmp,dstImage, matchMethod);
    normalize(dstImage,dstImage,0,1,cv::NORM_MINMAX,-1,cv::Mat());
    //通过函数minMaxLoc定位最匹配的位置
    double minVal,maxVal;
    cv::Point minLoc,maxLoc,matchLoc;
    minMaxLoc(dstImage, &minVal,&maxVal,&minLoc,&maxLoc,cv::Mat());
    
    //对于平方差匹配方法和归一化平方差匹配方法，越小的值有更高的匹配结果，而其余的方法相反。
    if(matchMethod==cv::TM_SQDIFF||matchMethod==cv::TM_SQDIFF_NORMED)
    {
        matchLoc=minLoc;
    }
    else
    {
        matchLoc=maxLoc;
    }
    //绘制矩形，并显示最终结果
    rectangle(src, matchLoc, cvPoint(matchLoc.x+ tmp.cols,matchLoc.y+ tmp.rows), cvScalar(0,0,255),2,8,0);
    src.copyTo(dst);
}

//***************************************************************************************

//                      利用ORB算法提取特征并利用FLANN进行匹配 实现特征匹配

//***************************************************************************************


void ImageDecMat::on_matchFeatures(cv::Mat src,cv::Mat tmp,cv::Mat& dst)
{
    //***************检测ORB特征点并在图像中提取物体的描述符***************8
    //定义参数
    cv::OrbFeatureDetector featureDetector;
    std::vector<cv::KeyPoint>keyPoints,keyPoints_templ;
    cv::Mat descriptors,descriptors_templ;
    
    //调用detect函数检测出特征关键点，保存在vector容器中
    featureDetector.detect(src, keyPoints);
    featureDetector.detect(tmp, keyPoints_templ);
    //计算描述符（特征向量）
    cv::OrbDescriptorExtractor featureExtractor;
    featureExtractor.compute(src, keyPoints, descriptors);
    featureExtractor.compute(tmp, keyPoints_templ, descriptors_templ);
    //匹配和测试描述符，获取两个最近邻的描述符
    cv::Mat matchIndex(descriptors.rows,2,CV_32SC1),matchDistance(descriptors.rows,2,CV_32FC1);
    //基于FLANN的描述符对象匹配
    cv::flann::Index flannIndex(descriptors_templ,cv::flann::LshIndexParams(12,20,2),cvflann::FLANN_DIST_HAMMING);
    flannIndex.knnSearch(descriptors, matchIndex, matchDistance, 2,cv::flann::SearchParams());//调用k近邻算法
    //根据劳氏算法，选出优秀的匹配
    std::vector<cv::DMatch>goodMatches;
    for(int i=0;i<matchDistance.rows;++i)
    {
        if(matchDistance.at<float>(i,0)<0.6 * matchDistance.at<float>(i,1))
        {
            cv::DMatch dmatches(i,matchIndex.at<int>(i,0),matchDistance.at<float>(i,0));
            goodMatches.push_back(dmatches);
            
        }
    }
    //绘制并显示匹配窗口
    drawMatches(src,keyPoints,tmp,keyPoints_templ,goodMatches,dst);
    
}

//***************************************************************************************

//利用轮廓信息实现形状匹配-其中canny边缘低阈值为100，高阈值为200，且对轮廓图使用模板匹配方法也是5:归一化系数匹配法

//***************************************************************************************



void ImageDecMat::on_shapeFeatures(cv::Mat src,cv::Mat tmp,cv::Mat& dst)
{
    int lowThresh=100;
    int highThresh=200;
    int matchMethod=5;
    cv::Mat out_grayImage1,out_grayImage2;
    std::vector<std::vector<cv::Point>>src_contours,tmp_contours,match_contours;
    std::vector<cv::Vec4i>src_hierarchy,tmp_hierarchy;
    //使用canny边缘检测，这里的阈值很关键，可调！
    Canny(src, out_grayImage1, lowThresh, highThresh,3);
    Canny(tmp, out_grayImage2, lowThresh, highThresh,3);
    //找到轮廓
    findContours(out_grayImage1, src_contours, src_hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE,cv::Point(0,0));
    findContours(out_grayImage2, tmp_contours, tmp_hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE,cv::Point(0,0));
    //计算矩
    std::vector<CvMoments>src_mu(src_contours.size());
    std::vector<CvMoments>tmp_mu(tmp_contours.size());
    for(unsigned int i=0;i<src_contours.size();++i)
    {
        src_mu[i]=moments(src_contours[i],false);
    }
    for(unsigned int i=0;i<tmp_contours.size();++i)
    {
        tmp_mu[i]=moments(tmp_contours[i],false);
    }
    //计算中心矩
    std::vector<cv::Point2f>src_mc(src_contours.size());
    std::vector<cv::Point2f>tmp_mc(tmp_contours.size());
    for(unsigned int i=0;i<src_contours.size();++i)
    {
        src_mc[i]=cv::Point2f(static_cast<float>(src_mu[i].m10/src_mu[i].m00),static_cast<float>(src_mu[i].m01/src_mu[i].m00));
    }
    for(unsigned int i=0;i<tmp_contours.size();++i)
    {
        tmp_mc[i]=cv::Point2f(static_cast<float>(tmp_mu[i].m10/tmp_mu[i].m00),static_cast<float>(tmp_mu[i].m01/tmp_mu[i].m00));
    }
    //计算Hu矩
    std::vector<CvHuMoments> src_hu(src_contours.size()),tmp_hu(tmp_contours.size());
    for(unsigned int i=0;i<src_contours.size();++i)
    {
        cvGetHuMoments(&src_mu[i], &src_hu[i]);
        std::cout<<"src_Hu:"<<src_hu[i].hu1<<std::endl;
    }
    for(unsigned int i=0;i<tmp_contours.size();++i)
    {
        cvGetHuMoments(&tmp_mu[i], &tmp_hu[i]);
        std::cout<<"tmp_Hu:"<<tmp_hu[i].hu1<<std::endl;
    }
    //利用Hu矩进行匹配
    double matchVal;
    for(unsigned int i=0;i<tmp_contours.size();++i)
    {
        matchVal=cv::matchShapes(src_contours[i],tmp_contours[i], 2, 0.0);
        if(matchVal>0.0)
        {
            match_contours.push_back(tmp_contours[i]);
        }
        std::cout<<"matchVal: "<<matchVal<<std::endl;
    }
    
    
    //***********************绘制轮廓*********************************
    cv::Mat dstImage=cv::Mat::zeros(out_grayImage1.size(), CV_8UC3);
    for(unsigned int i=0;i<src_contours.size();++i)
    {
        //随机生成颜色值
        cv::Scalar color=cv::Scalar(0,0,255);
        //绘制外层和内层轮廓
        drawContours(dstImage, src_contours, i, color,2,8,src_hierarchy,0,cv::Point());
        //绘制圆
        //circle(dstImage, src_mc[i], 4, color,-1,8,0);
        
    }
    cv::Mat dstImage1=cv::Mat::zeros(out_grayImage2.size(), CV_8UC3);
    for(unsigned int i=0;i<tmp_contours.size();++i)
    {
        //随机生成颜色值
        cv::Scalar color=cv::Scalar(0,0,255);
        //绘制外层和内层轮廓
        drawContours(dstImage1, tmp_contours, i, color,1,8,std::vector<cv::Vec4i>(),0,cv::Point());
    }
    
    //找到匹配位置
    cv::Mat LocImage;
    //5表示利用归一化相关系数匹配法
    cv::matchTemplate(dstImage, dstImage1, LocImage, matchMethod);
    cv::normalize(LocImage, LocImage,0,1,cv::NORM_MINMAX,-1,cv::Mat());
    cv::Point maxLocation;
    cv::minMaxLoc(LocImage,0,0,0,&maxLocation,cv::Mat());
    
    for(unsigned int i=0;i<match_contours.size();++i)
    {
        //绘制外层和内层轮廓
        drawContours(src, match_contours, i, cv::Scalar(0,0,255),2,8,std::vector<cv::Vec4i>(),0,cv::Point(maxLocation.x,maxLocation.y));
    }
    src.copyTo(dst);
    
}

